using System;
using System.Collections.Generic;
using System.Text;
using Interop = Microsoft.Feeds.Interop;
using System.Collections.ObjectModel;
using System.Windows.Media;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.IO;
using System.Windows.Media.Imaging;
using System.Threading;
using System.Collections;

namespace Ricciolo.PaperBoy.Feeds
{
    public class Feed : FeedBase, IFeed, IDelete
    {

        internal Interop.IFeed feed;
        internal Interop.IFeedEvents_Event watcher;

        private FeedItemCollection _items;
        private FeedFolder _parent;

        private ImageSource _favIcon;
        private Brush _favIconBrush;
        private bool favIconDownloaded;

        private bool _isDownloading;

        public Feed(FeedsManager feedsManager, string path)
            : base(feedsManager, path)
        {
            // Intercept self folder events
            this.watcher = (Interop.IFeedEvents_Event)this.feed.GetWatcher(Interop.FEEDS_EVENTS_SCOPE.FES_SELF_AND_CHILDREN_ONLY, Interop.FEEDS_EVENTS_MASK.FEM_FEEDEVENTS);
            this.watcher.Error += new Microsoft.Feeds.Interop.IFeedEvents_ErrorEventHandler(watcher_Error);
            this.watcher.FeedDownloading += new Microsoft.Feeds.Interop.IFeedEvents_FeedDownloadingEventHandler(watcher_FeedDownloading);
            this.watcher.FeedItemCountChanged += new Microsoft.Feeds.Interop.IFeedEvents_FeedItemCountChangedEventHandler(watcher_FeedItemCountChanged);
            this.watcher.FeedDownloadCompleted += new Microsoft.Feeds.Interop.IFeedEvents_FeedDownloadCompletedEventHandler(watcher_FeedDownloadCompleted);
            this.watcher.FeedRenamed += new Microsoft.Feeds.Interop.IFeedEvents_FeedRenamedEventHandler(watcher_FeedRenamed);
            this.watcher.FeedUrlChanged += new Microsoft.Feeds.Interop.IFeedEvents_FeedUrlChangedEventHandler(watcher_FeedUrlChanged);
        }

        #region Handlers

        void watcher_FeedDownloading(string Path)
        {
            _isDownloading = true;

            this.OnPropertyChanged("IsDownloading");

            if (!this.favIconDownloaded)
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(delegate
                {
                    FavIconGrabber gb = new FavIconGrabber();
                    gb.BeginGetFavIcon(this.Url, new AsyncCallback(OnGetFavIconGrabber), gb);
                }));
            }
        }

        void watcher_FeedUrlChanged(string Path)
        {
            this.Refresh();

            this.OnPropertyChanged("Url");
        }

        void watcher_FeedRenamed(string Path, string oldPath)
        {
            this.Path = Path;

            this.OnPropertyChanged("Name");
            this.OnPropertyChanged("Path");
        }

        void watcher_FeedDownloadCompleted(string Path, Microsoft.Feeds.Interop.FEEDS_DOWNLOAD_ERROR Error)
        {
            this.Refresh();
            _isDownloading = false;

            this.OnPropertyChanged("LastDownloadTime");
            this.OnPropertyChanged("LastBuildDate");
            this.OnPropertyChanged("LastWriteTime");
            this.OnPropertyChanged("LastDownloadError");
            this.OnPropertyChanged("IsDownloading");
        }

        void watcher_FeedItemCountChanged(string Path, int itemCountType)
        {
            this.Refresh();

            if (this._items != null && !this._items.MustReload)
                this.Items.Refresh(true);

            this.OnPropertyChanged("ItemCount");
            this.OnPropertyChanged("ReadedItemCount");
            this.OnPropertyChanged("UnreadItemCount");
            this.OnPropertyChanged("HasUnreadItems");
        }

        void watcher_Error()
        {
            this.Refresh();

            _isDownloading = false;

            this.OnPropertyChanged("IsDownloading");
            this.OnPropertyChanged("LastDownloadError");
        }

        #endregion

        #region Properties

        public override string Type
        {
            get { return typeof(Feed).Name; }
        }

        /// <summary>
        /// Retrieves an enumerator for items from the feed
        /// </summary>
        public FeedItemCollection Items
        {
            get
            {
                if (_items == null)
                    lock (this)
                    {
                        if (_items == null)
                            this._items = new FeedItemCollection(this);
                    }

                return this._items;
            }
        }

        /// <summary>
        /// Get if this feed has items
        /// </summary>
        public bool HasItems
        {
            get { return this._items != null && this._items.Count > 0; }
        }

        IList IFeed.Items
        {
            get { return this.Items; }
        }

        IFeedFolder IFeed.Parent
        {
            get { return this.Parent; }
        }

        /// <summary>
        /// Host favicon
        /// </summary>
        public ImageSource FavIcon
        {
            get
            {
                EnsureFavIcon();
                return this._favIcon;
            }
        }

        /// <summary>
        /// Host favicon
        /// </summary>
        public Brush FavIconBrush
        {
            get
            {
                EnsureFavIcon();
                return this._favIconBrush;
            }
        }

        /// <summary>
        /// Retrieves the display name of the feed
        /// </summary>
        public string Name
        {
            get { return this.feed.Name; }
            set
            {
                if (this.feed.Name != value)
                    this.feed.Rename(value);
            }
        }

        /// <summary>
        /// Retrieves the copyright from the feed
        /// </summary>
        public string Copyright
        {
            get { return this.feed.Copyright; }
        }

        /// <summary>
        /// Get if engine is downloading feed
        /// </summary>
        public bool IsDownloading
        {
            get { return _isDownloading; }
        }

        /// <summary>
        /// Get the parent folder
        /// </summary>
        public FeedFolder Parent
        {
            get { return _parent; }
        }

        /// <summary>
        /// Retrieves a phrase or sentence that describes the feed or item
        /// </summary>
        public string Description
        {
            get { return this.feed.Description; }
        }

        /// <summary>
        /// Sets or retrieves a value that determines whether enclosures are downloaded automatically
        /// </summary>
        public bool DownloadEnclosuresAutomatically
        {
            get { return this.feed.DownloadEnclosuresAutomatically; }
            set
            {
                if (this.DownloadEnclosuresAutomatically != value)
                {
                    this.feed.DownloadEnclosuresAutomatically = value;
                    this.OnPropertyChanged("DownloadEnclosuresAutomatically");
                }
            }
        }

        /// <summary>
        /// Retrieves the current download status of the feed or enclosure
        /// </summary>
        public FeedDownloadStatus DownloadStatus
        {
            get { return (FeedDownloadStatus)Enum.ToObject(typeof(FeedDownloadStatus), (int)this.feed.DownloadStatus); }
        }

        /// <summary>
        /// Retrieves the URL from which the feed, item, or enclosure was downloaded
        /// </summary>
        public Uri DownloadUrl
        {
            get { return new Uri(this.feed.DownloadUrl); }
        }

        /// <summary>
        /// Retrieves the image URL from the feed
        /// </summary>
        public Uri Image
        {
            get
            {
                if (this.feed.Image != null)
                {
                    Uri u;
                    if (Uri.TryCreate(this.feed.Image, UriKind.Absolute, out u))
                        return u;
                }
                return null;
            }
        }

        /// <summary>
        /// Sets or retrieves the update interval for the feed
        /// </summary>
        public int Interval
        {
            get { return this.feed.Interval; }
            set
            {
                if (this.Interval != value)
                {
                    this.feed.Interval = value;
                    this.OnPropertyChanged("Interval");
                }
            }
        }

        /// <summary>
        /// Sets or retrieves the update setting for the feed.
        /// </summary>
        public FeedInterval SyncSetting
        {
            get
            {
                return (FeedInterval)Enum.ToObject(typeof(FeedInterval), (int)this.feed.SyncSetting);
            }
            set
            {
                if (this.SyncSetting != value)
                {
                    this.feed.SyncSetting = (Interop.FEEDS_SYNC_SETTING)Enum.ToObject(typeof(Interop.FEEDS_SYNC_SETTING), (int)value);
                    this.OnPropertyChanged("SyncSetting");
                }
            }
        }

        /// <summary>
        /// Retrieves the language code from the feed
        /// </summary>
        public string Language
        {
            get { return this.feed.Language; }
        }

        /// <summary>
        /// Retrieves the last build date from the feed
        /// </summary>
        public DateTime LastBuildDate
        {
            get { return this.feed.LastBuildDate; }
        }

        /// <summary>
        /// Retrieves the last download error
        /// </summary>
        public FeedDownloadError LastDownloadError
        {
            get { return (FeedDownloadError)Enum.ToObject(typeof(FeedDownloadError), (int)this.feed.LastDownloadError); }
        }

        /// <summary>
        /// Retrieves the date and time of the last successful download
        /// </summary>
        public DateTime LastDownloadTime
        {
            get { return this.feed.LastDownloadTime; }
        }

        /// <summary>
        /// Retrieves the last time the feed changed
        /// </summary>
        public DateTime LastWriteTime
        {
            get { return this.feed.LastWriteTime; }
        }

        /// <summary>
        /// Retrieves the home page URL from the feed or item
        /// </summary>
        public string Link
        {
            get { return this.feed.Link; }
        }

        /// <summary>
        /// Retrieves the file system path where enclosures for the feed are downloaded
        /// </summary>
        public string LocalEnclosurePath
        {
            get { return this.feed.LocalEnclosurePath; }
        }

        /// <summary>
        /// Retrieves the GUID associated with this feed
        /// </summary>
        public Guid LocalId
        {
            get { return new Guid(this.feed.LocalId); }
        }

        /// <summary>
        /// Sets or retrieves the maximum number of downloaded items for the feed
        /// </summary>
        public int MaxItemCount
        {
            get { return this.feed.MaxItemCount; }
            set
            {
                if (this.MaxItemCount != value)
                {
                    this.feed.MaxItemCount = value;
                    this.OnPropertyChanged("MaxItemCount");
                }
            }
        }

        /// <summary>
        /// Retrieves the publication date and time of the feed or item
        /// </summary>
        public DateTime PubDate
        {
            get { return this.feed.PubDate; }
        }

        /// <summary>
        /// Retrieves the title from the feed or feed item
        /// </summary>
        public string Title
        {
            get { return this.feed.Title; }
        }

        /// <summary>
        /// Retrieves the time to live (TTL) from the feed
        /// </summary>
        public int Ttl
        {
            get { return this.feed.Ttl; }
        }

        /// <summary>
        /// Retrieves the count of items in this feed
        /// </summary>
        public int ItemCount
        {
            get { return this.feed.ItemCount; }
        }

        /// <summary>
        /// Retrieves the count of readed feed items
        /// </summary>
        public int ReadedItemCount
        {
            get { return this.ItemCount - this.UnreadItemCount; }
        }

        /// <summary>
        /// Get if there is unreaded items
        /// </summary>
        public bool HasUnreadItems
        {
            get { return this.UnreadItemCount > 0; }
        }

        /// <summary>
        /// Retrieves the count of unread feed items
        /// </summary>
        public int UnreadItemCount
        {
            get { return this.feed.UnreadItemCount; }
        }

        /// <summary>
        /// Sets or retrieves the URL of the feed or enclosure
        /// </summary>
        public Uri Url
        {
            get { return new Uri(this.feed.Url); }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");
                this.feed.Url = value.ToString();
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Downloads the feed or enclosure asynchronously
        /// </summary>
        public void AsyncDownload()
        {
            this.feed.AsyncDownload();
        }

        /// <summary>
        /// Cancels asynchronous download of the feed or enclosure
        /// </summary>
        public void CancelAsyncDownload()
        {
            this.feed.CancelAsyncDownload();
        }

        /// <summary>
        /// Deletes the feed, folder, or item
        /// </summary>
        public void Delete()
        {
            this.feed.Delete();
        }

        /// <summary>
        /// Downloads content for the feed
        /// </summary>
        public void Download()
        {
            this.feed.Download();
        }

        /// <summary>
        /// Sets the IsRead property to true for all feed items.
        /// </summary>
        public void MarkAllItemsRead()
        {
            if (this._items != null)
            {
                foreach (FeedItem item in this._items)
                {
                    item.IsRead = true;
                }
            }
            this.feed.MarkAllItemsRead();
        }

        /// <summary>
        /// Combines the specified XML with the feed
        /// </summary>
        /// <param name="feedXml">String that specifies the XML to be merged with the feed.</param>
        /// <param name="feedUrl">String that specifies the download URL to be used for security purposes</param>
        public void Merge(string feedXml, Uri feedUrl)
        {
            if (feedUrl == null)
                throw new ArgumentNullException("feedUrl");
            this.feed.Merge(feedXml, feedUrl.ToString());
        }

        /// <summary>
        /// Moves the feed or folder to a new location
        /// </summary>
        /// <param name="newParentPath">String that specifies the new parent folder</param>
        public void Move(string newParentPath)
        {
            this.feed.Move(newParentPath);
        }

        #endregion

        private delegate void OnMethodInvoker();

        private void OnGetFavIconGrabber(IAsyncResult ar)
        {
            FavIconGrabber gb = (FavIconGrabber)ar.AsyncState;
            Stream stream = gb.EndGetFavIcon(ar);

            if (stream != null)
            {
                try
                {
                    CreateFavIconFromStream(stream);

                    this.favIconDownloaded = true;
                }
                catch (Exception)
                {
                    this.favIconDownloaded = false;
                }
            }
            else
            {
                //InvalidateFavIcon();
            }
        }

        private void InvalidateFavIcon()
        {
            this._favIconBrush = null;
            this._favIcon = null;
            this.favIconDownloaded = false;
            this.OnPropertyChanged("FavIcon");
            this.OnPropertyChanged("FavIconBrush");
        }

        private void EnsureFavIcon()
        {
            if (this.favIconDownloaded) return;

            // Create icon if exists
            try
            {
                string pathIcon = GetPathFavIcon();
                if (File.Exists(pathIcon))
                {
                    _favIcon = BitmapFrame.Create(new Uri(pathIcon, UriKind.Absolute), BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
                    _favIconBrush = new ImageBrush(_favIcon);
                }
            }
            catch (FileFormatException)
            { }
            catch (NotSupportedException)
            { }   
            catch (IOException)
            { }
        }

        private void CreateFavIconFromStream(Stream stream)
        {
            MemoryStream ms;
            try
            {
                ms = new MemoryStream();
                // Read all bytes
                byte[] buffer = new byte[0x2000];
                string pathIcon = GetPathFavIcon();
                using (Stream file = File.Create(pathIcon, buffer.Length))
                {
                    // Write to file
                    int i;
                    while ((i = stream.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        ms.Write(buffer, 0, i);
                        file.Write(buffer, 0, i);
                    }
                }
            }
            finally
            {
                if (stream != null)
                    stream.Dispose();
            }

            this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new OnMethodInvoker(delegate
            {
                try
                {
                    try
                    {
                        // Create bitmap and brush
                        BitmapFrame bf = BitmapFrame.Create(ms, BitmapCreateOptions.None, BitmapCacheOption.None);
                        this._favIcon = bf;
                        this._favIconBrush = new ImageBrush(this._favIcon);
                    }
                    finally
                    {
                        if (ms != null)
                            ms.Dispose();
                    }
                }
                catch (Exception)
                {
                    // Disable any exception
                    this._favIcon = null;
                    this._favIconBrush = null;
                }

                this.OnPropertyChanged("FavIcon");
                this.OnPropertyChanged("FavIconBrush");
            }));
        }

        private string GetPathFavIcon()
        {
            string dir = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"PaperBoy\Icons");
            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);

            return System.IO.Path.ChangeExtension(System.IO.Path.Combine(dir, this.LocalId.ToString()), ".ico");
        }

        protected override void Refresh()
        {
            try
            {
                this.feed = (Interop.IFeed)this.feedsManager.feedsManager.GetFeed(this.Path);
                this._parent = (FeedFolder)this.feedsManager.GetFeedByPathFromCache(((Interop.IFeedFolder)this.feed.Parent).Path);
            }
            catch (System.IO.FileNotFoundException)
            { }
            this.OnPropertyChanged("Parent");
        }
    }

    public enum FeedDownloadStatus
    {
        None = 0,
        Pending = 1,
        Downloading = 2,
        Downloaded = 3,
        Failed = 4,
    }

    public enum FeedInterval
    {
        /// <summary>
        /// Use the system-defined DefaultInterval value.
        /// </summary>
        Default = 0,
        /// <summary>
        /// Use the Interval value defined by the feed. 
        /// </summary>
        Interval = 1,
        /// <summary>
        /// Do not automatically update the feed. Use Download to manually update the feed
        /// </summary>
        Manually = 2
    }

    public enum FeedDownloadError
    {
        None = 0,
        DownloadFailed = 1,
        InvalidFeedFormat = 2,
        NormalizationFailed = 3,
        PersistenceFailed = 4,
        DownloadBlocked = 5,
        Canceled = 6,
        UnsupportedAuth = 7,
        BackgroundDownloadDisabled = 8,
        NotExist = 9,
        UnsupportedMSXml = 10,
        UnsupportedDtd = 11,
        DownloadSizeLimitExceteed = 12,
    }

}
